/*
 * Unidata Platform Community Edition
 * Copyright (c) 2013-2020, UNIDATA LLC, All rights reserved.
 * This file is part of the Unidata Platform Community Edition software.
 *
 * Unidata Platform Community Edition is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Unidata Platform Community Edition is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */

package org.unidata.mdm.data.service.segments.records.delete;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.unidata.mdm.core.type.calculables.CalculableHolder;
import org.unidata.mdm.core.type.calculables.ModificationBox;
import org.unidata.mdm.core.type.data.OperationType;
import org.unidata.mdm.core.type.data.RecordStatus;
import org.unidata.mdm.core.type.timeline.Timeline;
import org.unidata.mdm.core.util.SecurityUtils;
import org.unidata.mdm.data.context.DeleteRequestContext;
import org.unidata.mdm.data.module.DataModule;
import org.unidata.mdm.data.service.impl.RecordComposerComponent;
import org.unidata.mdm.data.type.calculables.impl.DataRecordHolder;
import org.unidata.mdm.data.type.data.OriginRecord;
import org.unidata.mdm.data.type.data.OriginRecordInfoSection;
import org.unidata.mdm.data.type.data.impl.OriginRecordImpl;
import org.unidata.mdm.data.type.keys.RecordEtalonKey;
import org.unidata.mdm.data.type.keys.RecordKeys;
import org.unidata.mdm.data.type.keys.RecordOriginKey;
import org.unidata.mdm.data.type.timeline.RecordTimeline;
import org.unidata.mdm.system.service.PlatformConfiguration;
import org.unidata.mdm.system.type.pipeline.Point;
import org.unidata.mdm.system.type.pipeline.Start;
import org.unidata.mdm.system.type.runtime.MeasurementPoint;
import org.unidata.mdm.system.type.support.IdentityHashSet;

/**
 * @author Mikhail Mikhailov on Nov 10, 2019
 */
@Component(RecordDeleteTimelineExecutor.SEGMENT_ID)
public class RecordDeleteTimelineExecutor extends Point<DeleteRequestContext> {
    /**
     * This segment ID.
     */
    public static final String SEGMENT_ID = DataModule.MODULE_ID + "[RECORD_DELETE_TIMELINE]";
    /**
     * Localized message code.
     */
    public static final String SEGMENT_DESCRIPTION = DataModule.MODULE_ID + ".record.delete.timeline.description";
    /**
     * The composer.
     */
    @Autowired
    private RecordComposerComponent recordComposerComponent;
    /**
     * Version numbers.
     */
    @Autowired
    private PlatformConfiguration platformConfiguration;
    /**
     * Constructor.
     * @param id
     * @param description
     */
    public RecordDeleteTimelineExecutor() {
        super(SEGMENT_ID, SEGMENT_DESCRIPTION);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void point(DeleteRequestContext ctx) {

        MeasurementPoint.start();
        try {

            // Delete irreversible, wont have next timeline
            // Origin is not yet handled properly
            if (ctx.isWipe() || ctx.isInactivateOrigin()) {
                return;
            }

            // Create next timeline to master timeline
            RecordKeys keys = ctx.keys();
            Date ts = ctx.timestamp();
            OperationType operationType = ctx.operationType();
            String user = SecurityUtils.getCurrentUserName();

            Timeline<OriginRecord> current = ctx.currentTimeline();
            Timeline<OriginRecord> next = null;
            ModificationBox<OriginRecord> box = ctx.modificationBox();

            // Period
            if (ctx.isInactivatePeriod()) {

                // 'current' is restricted to period bounds
                Set<CalculableHolder<OriginRecord>> revisions = new IdentityHashSet<>();
                List<CalculableHolder<OriginRecord>> result = new ArrayList<>();
                current
                    .selectBy(ctx.getValidFrom(), ctx.getValidTo())
                    .forEach(i -> {

                        List<CalculableHolder<OriginRecord>> existing = i.toList();
                        existing.stream()
                                .filter(v -> v.getStatus() == RecordStatus.ACTIVE)
                                .map(v -> {

                                    // Sort out revisions, which were already added.
                                    if (revisions.contains(v)) {
                                        return null;
                                    }

                                    revisions.add(v);

                                    RecordOriginKey rok = (RecordOriginKey) v.getOriginKey();
                                    return new OriginRecordImpl()
                                        .withDataRecord(v.getValue())
                                        .withInfoSection(new OriginRecordInfoSection()
                                                .withValidFrom(ctx.getValidFrom())
                                                .withValidTo(ctx.getValidTo())
                                                .withCreateDate(rok.getCreateDate())
                                                .withCreatedBy(rok.getCreatedBy())
                                                .withUpdateDate(ts)
                                                .withUpdatedBy(user)
                                                .withRevision(0)
                                                .withStatus(RecordStatus.INACTIVE)
                                                .withOperationType(operationType == null ? OperationType.DIRECT : operationType)
                                                .withShift(v.getValue().getInfoSection().getShift())
                                                .withMajor(platformConfiguration.getPlatformMajor())
                                                .withMinor(platformConfiguration.getPlatformMinor())
                                                .withOriginKey(rok));
                                })
                                .filter(Objects::nonNull)
                                .map(DataRecordHolder::new)
                                .collect(Collectors.toCollection(() -> result));
                    });

                // Original keys traditionally remain unchanged until the end of the PL
                // Set the new keys state to the NEXT timeline
                RecordKeys nextKeys = RecordKeys.builder(keys)
                    .updateDate(ts)
                    .updatedBy(user)
                    .build();

                if (Objects.nonNull(box)) {
                    box.toModifications().forEach((k, v) -> v.forEach(result::add));
                }

                next = current.merge(new RecordTimeline(nextKeys, result));
                next.forEach(i -> recordComposerComponent.toEtalon(nextKeys, i));

            // Whole record
            } else {

                // Original keys traditionally remain unchanged until the end of the PL
                // Set the new keys state to the NEXT timeline
                RecordKeys nextKeys = RecordKeys.builder(keys)
                    .etalonKey(RecordEtalonKey.builder(keys.getEtalonKey())
                        .status(RecordStatus.INACTIVE)
                        .build())
                    .updateDate(ts)
                    .updatedBy(user)
                    .build();

                List<CalculableHolder<OriginRecord>> target = new ArrayList<>(current.getCalculables());
                if (Objects.nonNull(box)) {
                    box.toModifications().forEach((k, v) -> v.forEach(target::add));
                }

                next = new RecordTimeline(nextKeys, target);
                next.forEach(i -> recordComposerComponent.toEtalon(nextKeys, i));
            }

            ctx.nextTimeline(next);
        } finally {
            MeasurementPoint.stop();
        }
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean supports(Start<?, ?> start) {
        return DeleteRequestContext.class.isAssignableFrom(start.getInputTypeClass());
    }
}
